package demo.javax.script;

import org.junit.Test;

import javax.script.Compilable;
import javax.script.CompiledScript;
import javax.script.Invocable;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineFactory;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
import java.util.List;

public class ScriptEngineDemo {

    @Test
    public void testJavascript() {
        String regular = "function regular(a,b){ return a+b}";
        ScriptEngine engine = new ScriptEngineManager().getEngineByName("javascript");
        try {
            engine.eval(regular);
            if (engine instanceof Invocable) {
                Invocable invoke = (Invocable) engine;
                Object result = invoke.invokeFunction("regular", 5, 6);
                System.out.println(result);
            } else {
                System.out.println("error");
            }
        } catch (ScriptException e) {
            System.out.println("表达式runtime错误:" + e.getMessage());
        } catch (NoSuchMethodException e) {
            throw new RuntimeException(e);
        }

    }

    @Test
    public void testScriptEngineFactory() {
        ScriptEngineManager manager = new ScriptEngineManager();
        // 得到所有的脚本引擎工厂
        List<ScriptEngineFactory> factories = manager.getEngineFactories();
        for (ScriptEngineFactory factory : factories) {
            System.out.println("------------------------------" + factory.getEngineName());
            // 打印脚本信息
            System.out.printf("Name: %s%n" +
                            "Version: %s%n" +
                            "Language name: %s%n" +
                            "Language version: %s%n" +
                            "Extensions: %s%n" +
                            "Mime types: %s%n" +
                            "Names: %s%n",
                    factory.getEngineName(),
                    factory.getEngineVersion(),
                    factory.getLanguageName(),
                    factory.getLanguageVersion(),
                    factory.getExtensions(),
                    factory.getMimeTypes(),
                    factory.getNames());

            // 得到当前的脚本引擎
            ScriptEngine engine = factory.getScriptEngine();

        }
    }

    @Test
    public void testScriptEngine() {
        ScriptEngineManager manager = new ScriptEngineManager();
        // 根据扩展名得到脚本引擎
        ScriptEngine engine1 = manager.getEngineByExtension("js");
        // 根据Mime类型得到脚本引擎
        ScriptEngine engine2 = manager.getEngineByMimeType("text/javascript");
        // 根据名称得到脚本引擎
        ScriptEngine engine3 = manager.getEngineByName("javascript");

    }

    @Test
    public void testJavaScript() throws ScriptException {
        ScriptEngineManager manager = new ScriptEngineManager();
        // 得到javascript脚本引擎

        ScriptEngine engine = manager.getEngineByName("javascript");
        String script = "var date = new Date();" + "date.getHours();";
        // 开始运行脚本，并返回当前的小时
        Double hour = (Double) engine.eval(script);
        String msg;
        // 将小时转换为问候信息
        if (hour < 10) {
            msg = "上午好";
        } else if (hour < 16) {
            msg = "下午好";
        } else if (hour < 20) {
            msg = "晚上好";
        } else {
            msg = "晚安";
        }
        System.out.printf("小时%s: %s%n", hour, msg);
    }

    /**
     * 和脚本语言进行交互
     * <p>
     * put 有两个参数，一个是脚本变量名，另一个是变量的值，这个值是Object类型，因此，可以传递任何值。
     * <p>
     * get 有一个参数，就是脚本变量的名。
     */
    @Test
    public void testReverseString() throws ScriptException {
        ScriptEngineManager manager = new ScriptEngineManager();
        // 建立javascript脚本引擎

        ScriptEngine engine = manager.getEngineByName("javascript");
        String script = "var output ='' ;" +
                "for (i = 0; i <= name.length; i++) {" +
                " output = name.charAt(i) + output" +
                "}";
        // 将变量name和变量值abcdefg传给javascript脚本
        engine.put("name", "你好");
        // 开始执行脚本
        engine.eval(script);
        // 得到output变量的值

        String name = (String) engine.get("output");
        System.out.printf("被翻转后的字符串：%s", name);

    }

    /**
     * 让脚本运行得更快
     * <p>
     * 众所周知，解释运行方式是最慢的运行方式。上述的几个例子无一例外地都是以解释方式运行的。
     * 由于Java EE 6的脚本引擎可以支持任何实现脚本引擎接口的语言。有很多这样的语言提供了编译功能，
     * 也就是说，在运行脚本之前要先将这些脚本进行编译(这里的编译一般将不是生成可执行文件，而只是在内存中编译成更容易运行的方式)，然后再执行。
     * 如果某段脚本要运行之交多次的话，使用这种方式是非常快的。我们可以使用 ScriptEngine的compile方法进行编译。
     * 并不是所有脚本引擎都支持编译，只有实现了Compilable接口的脚本引擎才可以使用 compile进行编译，否则将抛出一个错误。
     *
     * @throws ScriptException
     */
    @Test
    public void testCompileScript() throws ScriptException {
        ScriptEngineManager manager = new ScriptEngineManager();
        ScriptEngine engine = manager.getEngineByName("javascript");
        engine.put("counter", 0); // 向javascript传递一个参数
        // 判断这个脚本引擎是否支持编译功能
        if (engine instanceof Compilable) {
            Compilable compEngine = (Compilable) engine;
            // 进行编译
            CompiledScript script = compEngine.compile("function count() { " +
                    " counter = counter +1; " +
                    " return counter; " +
                    "}; count();");
            System.out.printf("Counter: %s%n", script.eval());
            System.out.printf("Counter: %s%n", script.eval());
            System.out.printf("Counter: %s%n", script.eval());
        } else {
            System.err.println("这个脚本引擎不支持编译!");
        }
    }

    /**
     * 动态调用脚本语言的方法
     * <p>
     * 如果脚本中有多个函数或想通过用户的输入来决定调用哪个函数，这就需要使用invoke方法进行动态调用。
     * 和编译一样，脚本引擎必须实现Invocable接口才可以动态调用脚本语言中的方法。
     */
    @Test
    public void testInvocable() {
        ScriptEngineManager manager = new ScriptEngineManager();
        ScriptEngine engine = manager.getEngineByName("javascript");
        String name = "abcdefg";
        if (engine instanceof Invocable) {
            try {
                engine.eval("function reverse(name) {" +
                        " var output =' ';" +
                        " for (i = 0; i <= name.length; i++) {" +
                        " output = name.charAt(i) + output" +
                        " } return output;}");
                Invocable invokeEngine = (Invocable) engine;
                Object o = invokeEngine.invokeFunction("reverse", name);
                System.out.printf("翻转后的字符串：%s", o);
            } catch (NoSuchMethodException e) {
                System.err.println(e);
            } catch (ScriptException e) {
                System.err.println(e);
            }
        } else {
            System.err.println("这个脚本引擎不支持动态调用");
        }
    }

    /**
     * 动态实现接口
     * <p>
     * 脚本引擎还有一个更吸引的功能，那就是动态实现接口。如我们要想让脚本异步地执行，即通过多线程来执行，那InvokeEngine类必须实现 Runnable接口才可以通过Thread启动多线程。
     * 因此，可以通过getInterface方法来使InvokeEngine动态地实现 Runnable接口。
     */
    @Test
    public void testInterface() throws ScriptException, InterruptedException {
        ScriptEngineManager manager = new ScriptEngineManager();
        ScriptEngine engine = manager.getEngineByName("javascript");
        engine.eval("function run() {print('异步调用');}");
        Invocable invokeEngine = (Invocable) engine;
        Runnable runner = invokeEngine.getInterface(Runnable.class);
        Thread t = new Thread(runner);
        t.start();
        t.join();
    }

}